﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using umbraco;
using System.Xml.Linq;
using System.Xml.XPath;
using umbraco.cms.businesslogic.web;
using System.Xml;
using System.Web;
using System.IO;
using umbraco.cms.businesslogic.media;
using System.Drawing;
using umbraco.BusinessLogic;
using uHelpsy.Core.Helpers;
using umbraco.presentation.channels.businesslogic;



namespace uHelpsy.Core
{

    /// <summary>
    /// Helper methods for creating, updating, and getting Umbraco nodes.
    /// </summary>
    public static class UmbracoAPIHelper
    {

        #region Document methods

        #region IEnumerable<Document> GetChildrenWherePropertyIs(int docId, string propertyName, string value)
        /// <summary>
        /// Gets children who have the property value.
        /// This method is useful for checking unpublished nodes.
        /// </summary>
        /// <param name="docId">The id of the document</param>
        /// <param name="propertyName">Name of the property</param>
        /// <param name="value">Value to check property against</param>
        /// <returns>IEnumerable<Document></returns>
        public static IEnumerable<Document> GetChildrenWherePropertyIs(int docId, string propertyName, string value)
        {
            Document doc = new Document(docId);
            return doc.GetChildrenWherePropertyIs(propertyName, value);
        }

        #endregion


        #region object GetPropertyOrDefault(int docId, string propertyName)
        /// <summary>
        /// Gets the value of a property, or null when it does not exist.
        /// This method is useful for checking unpublished nodes.
        /// </summary>
        /// <param name="docId">Id of the Document</param>
        /// <param name="propertyName">The name of the property to get the value of</param>
        /// <returns>The value of the property, or null</returns>
        public static object GetPropertyOrDefault(int docId, string propertyName)
        {
            Document doc = new Document(docId);
            return doc.GetPropertyOrDefault(propertyName);
        }

        #endregion

        #endregion


        #region CreateMedia
        /// <summary>
        /// Creates a folder under the media folder, saves the file there, and creates a media node where applicable.
        /// </summary>
        /// <param name="alias">Media alias</param>
        /// <param name="filePath">Path to the file</param>
        /// <param name="bytes">The file bytes</param>
        /// <param name="properties">Dictionary of properties</param>
        /// <param name="parentId">Id of the parent</param>
        /// <returns>The new Media</returns>
        public static Media CreateMedia(string alias, string filePath, byte[] bytes, Dictionary<string, string> properties, int parentId)
        {
            // create the Media Node
            Media m = Media.MakeNew(Path.GetFileNameWithoutExtension(filePath), MediaType.GetByAlias(alias), User.GetUser(0), parentId);

            // save bytes and properties
            m = UpdateMedia(m, filePath, bytes, properties, parentId);

            return m;
        }

        #endregion



        #region UpdateMedia

        #region Media UpdateMedia(Media m, string filePath, byte[] bytes, Dictionary<string, string> properties, int parentId)
        /// <summary>
        /// Updates Media with new file and properties.
        /// </summary>
        /// <param name="m">The Media</param>
        /// <param name="filePath">Path to the file</param>
        /// <param name="bytes">The file bytes</param>
        /// <param name="properties">Dictionary of properties</param>
        /// <param name="parentId">Id of the parent</param>
        /// <returns>The updated Media</returns>
        public static Media UpdateMedia(Media m, string filePath, byte[] bytes, Dictionary<string, string> properties, int parentId)
        {
            // get filename
            string filename = Path.GetFileName(filePath);
            string folder = "/media/" + m.getProperty("umbracoFile").Id;

            // ensure folder exists
            string fileSysFolder = HttpContext.Current.Server.MapPath(folder + "/");
            if (!Directory.Exists(fileSysFolder))
                Directory.CreateDirectory(fileSysFolder);

            // get full file path
            string fullFilePath = fileSysFolder + "/" + filename;
            
            // save the file
            File.WriteAllBytes(fullFilePath, bytes);

            // save properties
            m.getProperty("umbracoFile").Value = folder + "/" + filename;
            m = UpdateMediaProperties(m, properties);
            m.getProperty("umbracoExtension").Value = Path.GetExtension(filePath).Substring(1);
            m.getProperty("umbracoBytes").Value = new FileInfo(fullFilePath).Length.ToString();

            // when file is an image, get the width and height
            using (Image image = Drawing.GetImageOrDefault(fullFilePath))
            {
                if (image != null)
                {
                    m.getProperty("umbracoWidth").Value = image.Width.ToString();
                    m.getProperty("umbracoHeight").Value = image.Height.ToString();

                    // make thumbnail
                    Drawing.CreateAndSaveThumbnail(fileSysFolder, Path.GetFileNameWithoutExtension(filename) + "_thumb" + Path.GetExtension(filename), bytes, 100, 100);
                }
            }

           
            m.XmlGenerate(new XmlDocument());

            // save the node!
            m.Save();

            return m;
        }

        #endregion


        #region Media UpdateMedia(int id, string filePath, byte[] bytes, Dictionary<string, string> properties, int parentId)
        /// <summary>
        /// Updates Media with new file and properties.
        /// </summary>
        /// <param name="m">The Media</param>
        /// <param name="filePath">Path to the file</param>
        /// <param name="bytes">The file bytes</param>
        /// <param name="properties">Dictionary of properties</param>
        /// <param name="parentId">Id of the parent</param>
        /// <returns>The updated Media</returns>
        public static Media UpdateMedia(int id, string filePath, byte[] bytes, Dictionary<string, string> properties, int parentId)
        {
            // get the Media Node
            Media m = new Media(id);

            // save bytes and properties
            m = UpdateMedia(m, filePath, bytes, properties, parentId);

            return m;
        }

        #endregion


        #region Media UpdateMedia(int id, Dictionary<string, string> properties, int parentId)
        /// <summary>
        /// Updates Media with new properties.
        /// </summary>
        /// <param name="m">The Media</param>
        /// <param name="properties">Dictionary of properties</param>
        /// <param name="parentId">Id of the parent</param>
        /// <returns>The updated Media</returns>
        public static Media UpdateMedia(int id, Dictionary<string, string> properties, int parentId)
        {
            // get the Media Node
            Media m = new Media(id);

            // save bytes and properties
            m = UpdateMediaProperties(m, properties);

            return m;
        }

        #endregion


        #endregion



        #region GetMediaFromPath

        /// <summary>
        /// Gets the media node from the media path.
        /// Warning! This is HELLA inefficient and will take many seconds to iterate over a large collection.
        /// 
        /// Is there a better way to do this?!?!??!
        /// </summary>
        /// <param name="mediaPath"></param>
        /// <returns></returns>
        public static Media GetMediaFromPath(string mediaPath, string alias)
        {
            // get the folder 
            int folder = int.Parse(mediaPath.Split("/".ToCharArray())[2]);

            var media = Media.GetMediaOfMediaType(MediaType.GetByAlias(alias).Id)
                            .Where(x => (int)x.getProperty("umbracoFile").Id == folder)
                            .FirstOrDefault();
            return media;
        }
        
        #endregion



        #region CreateContentNode

        #region Document CreateContentNode(string nodeName, string nodeTypeAlias, Dictionary<string, object> properties, string parentXPath, bool publish)
        /// <summary>
        /// Creates a content node.
        /// </summary>
        /// <param name="nodeName">The node name</param>
        /// <param name="nodeTypeAlias">The nodeTypeAlias</param>
        /// <param name="properties">A dictionary of node properties</param>
        /// <param name="parentId">The XPath to the parent node</param>
        /// <param name="publish">Publish or just save?</param>
        /// <returns>The new document</returns>
        public static Document CreateContentNode(string nodeName, string nodeTypeAlias, Dictionary<string, object> properties, string parentXPath, bool publish)
        {
            int parentId = GetNodeIDByXPath(parentXPath);
            return CreateContentNode(nodeName, nodeTypeAlias, properties, parentId, publish);
        }
        #endregion


        #region Document CreateContentNode(string nodeName, string nodeTypeAlias, Dictionary<string, object> properties, int parentId, bool publish)
        /// <summary>
        /// Creates a content node.
        /// </summary>
        /// <param name="nodeName">The node name</param>
        /// <param name="nodeTypeAlias">The nodeTypeAlias</param>
        /// <param name="properties">A dictionary of node properties</param>
        /// <param name="parentId">The parent node</param>
        /// <param name="publish">Publish or just save?</param>
        /// <returns>document</returns>
        public static Document CreateContentNode(string nodeName, string nodeTypeAlias, Dictionary<string, object> properties, int parentId, bool publish)
        {
            // get doc type
            DocumentType docType = DocumentType.GetByAlias(nodeTypeAlias);

            // create new content node
            Document doc = Document.MakeNew(nodeName, docType, User.GetUser(0), parentId);

            // load properties and saves
            doc = UpdateContentNode(doc, properties, publish);

            // triggers save event!
            //doc.Save();
            
            return doc;
        }


        #endregion


        #endregion



        #region UpdateContentNode

        #region UpdateContentNode

        /// <summary>
        /// Updates properties in a content node.
        /// </summary>
        /// <param name="nodeName">The document</param>
        /// <param name="properties">A dictionary of node properties</param>
        /// <param name="publish">Publish or just save?</param>
        /// <returns></returns>
        public static Document UpdateContentNode(Document doc, Dictionary<string, object> properties, bool publish)
        {
            // save properties
            doc = UpdateDocumentProperties(doc, properties);

            // save the node
            //doc.Save();

            // publish the node
            if (publish)
            {
                doc.Publish(User.GetUser(0));
                umbraco.library.UpdateDocumentCache(doc.Id);
            }
            return doc;
        }


        #endregion


        #region UpdateContentNode
        /// <summary>
        /// Updates properties in a content node.
        /// </summary>
        /// <param name="nodeName">The document id</param>
        /// <param name="properties">A dictionary of node properties</param>
        /// <param name="publish">Publish or just save?</param>
        /// <returns>document</returns>
        public static Document UpdateContentNode(int id, Dictionary<string, object> properties, bool publish)
        {
            // get the doc
            Document doc = new Document(id);

            // load properties and saves
            return UpdateContentNode(doc, properties, publish);
        }

        #endregion


        #endregion




        #region GetNodeIDByXPath

        /// <summary>
        /// Gets a node id by XPath
        /// </summary>
        /// <param name="xPath"></param>
        /// <returns></returns>
        public static int GetNodeIDByXPath(string xPath)
        {
            return (int)GetNodeByXPath(xPath).Attribute("id");
        }

        #endregion


        #region GetNodeByXpath

        /// <summary>
        /// Gets node by it's XPath
        /// </summary>
        /// <param name="xPath"></param>
        /// <returns></returns>
        public static XElement GetNodeByXPath(string xPath)
        {
            XPathNodeIterator xml = library.GetXmlNodeByXPath(xPath);
            if (xml.Count == 0)
            {
                return null;
            }
            return UmbToXElement(xml);
        }

        #endregion


        #region GetNodeById

        /// <summary>
        /// Gets node by it's Id
        /// </summary>
        /// <param name="nodeId"></param>
        /// <returns></returns>
        public static XElement GetNodeById(int nodeId)
        {
            XPathNodeIterator xml = library.GetXmlNodeById(nodeId.ToString());
            if (xml.Count == 0)
            {
                return null;
            }
            return UmbToXElement(xml);
        }

        #endregion

        #region Private methods

        #region UpdateDocumentProperties
        /// <summary>
        /// Iterates over properties and loads them into the document properties.
        /// </summary>
        /// <param name="doc">The document</param>
        /// <param name="properties">A dictionary of properties</param>
        /// <returns>The updated Document</returns>
        private static Document UpdateDocumentProperties(Document doc, Dictionary<string, object> properties)
        {
            foreach (var key in properties.Keys)
            {
                doc.getProperty(key).Value = properties[key];
            }
            return doc;
        }

        #endregion



        #region UpdateMediaProperties
        /// <summary>
        /// Iterates over properties and loads them into the document properties.
        /// </summary>
        /// <param name="m">The Media node to update</param>
        /// <param name="properties">A dictionary of properties</param>
        /// <returns>The updated Media</returns>
        private static Media UpdateMediaProperties(Media m, Dictionary<string, string> properties)
        {
            foreach (var key in properties.Keys)
            {
                m.getProperty(key).Value = properties[key];
            }
            return m;
        }

        #endregion



        #region UmbToXElement

        /// <summary>
        /// Parses XPathNodeIterator to XElement.
        /// </summary>
        /// <param name="xml">The XPathNodeIterator to parse</param>
        /// <returns>An XElement representation of the XPathNodeIterator</returns>
        private static XElement UmbToXElement(XPathNodeIterator xml)
        {
            // no xml so return null
            if (xml.Count == 0)
                return null;

            // only one node
            if (xml.Count == 1)
            {
                // there is nothing in this node so return null
                if (!xml.MoveNext())
                    return null;

                // the node is the umbraco root node : <root id="-1">
                if (xml.Current.InnerXml.StartsWith("<root"))
                    return null;

                // return the single node XDocument
                return XDocument.Parse(xml.Current.OuterXml).Elements().First<XElement>();
            }

            // create the root node
            XElement rootNode = new XElement("nodes");

            // parse xml
            while (xml.MoveNext())
            {
                rootNode.Add(XElement.Parse(xml.Current.OuterXml));
            }

            return rootNode;
        }

        #endregion

        #endregion
    }
}
